/*
 * EK.modelLocator: An object to promote model shareability and observation
 * 
 * Enrichment Kit @ek.version@ (http://enrichmentkit.com)
 * Copyright (c) 2008 Pink Hominid
 * Licensed under terms of the MIT License (LICENSE.txt)
 * 
 * IE 6.0+, FF 1.5+ Tested
 * 
 * Known Issues:
 * 
 * IE 6.0+ >> Workaround for missing Object.watch requires HTML DOM
 * 
 * TODO:
 * 
 * ('+' = done but need tests!)
 * + automatically setup watch on any new nested objects that replaced existing
 *   properties or were added as new property values
 * + purge in depth
 * 
 * - pure array change notification (right now converting Arrays to array-like
 *   Objects (alternatively ease the issue of missing Array methods)
 * - come up with a cleaner method of watching added props in Mozilla
 * - remove dependency on eval
 */
EK.modelLocator = new function EkModelLocator() {
    var cache = {};
	var refCounter = {};
	var ieMeta;
	if (EK.browser.msie) { ieMeta = EK.tag("head")[0].appendChild(document.createElement("meta")); }
	
    this.mFactory = {
        get: function (mName) {
            var m = eval(mName);
            if (typeof m == "function") { m = new m(); }
            return m;
        }       
    };
    
    this.x = function (name) {
        var model = undefined;
        if (name) {
            model = cache[name];
            if (!model) {
                model = this.mFactory.get(name);
				if (isInterestingObject(model)) {
					cache[name] = model = watch(model, EK.extend(model, new EK.Eventable()));
				}
            }
			refCounter[name] = refCounter[name] || 0;
			refCounter[name]++;
        }
        return model;
    };
	
    this.purgeModel = function (name) {
        var model = undefined;
        if (name) {
            model = cache[name];
            if (model) {
                refCounter[name]--;
                if (refCounter[name] <= 0) {
                    model.unbind();
					unwatch(model);
                    cache[name] = model = undefined;
                    refCounter[name] = undefined;
                }
            }
        }
    };

    function isInterestingObject(thing) {
        return (typeof thing != 'undefined' &&
                thing !== null &&
                typeof thing == 'object' &&
                thing.constructor != Date);
    }

    function unwatch(obj){
        for (var prop in obj) {
			if (ieMeta && typeof ieMeta[prop] != 'undefined') { continue; }
            var nested = obj[prop];
            if (isInterestingObject(nested)) {
                unwatch(nested);
            }
        }
        purgeWatchedObject(obj);
    }
    
    function purgeWatchedObject(obj) {
        if (EK.browser.msie) {
            if (EK.tag("head")[0].contains(obj)) {
                EK.purge(EK.tag("head")[0].removeChild(obj));
            }
        } else {
            for (var propertyName in obj) {
                obj.unwatch(propertyName);
            }
        }
    }

	function watch(obj, trigger) {
		// watch the object's props
		for (var prop in obj) {
            var nested = obj[prop];
			if (isInterestingObject(nested)) {
				obj[prop] = watch(nested, trigger);
			}
		}
		obj = watchObjectForChange(obj, trigger);
		return obj;
	}
	
	function watchObjectForChange(obj, triggerObj) {
        //console.debug('watchObjectForChange');
		if (EK.browser.msie) {
            var watchableObj = document.createElement("meta");
			// if obj is an Array it will be converted into an 'array-like'
			// Object (i.e. has length property and properties numbered 0 to length-1)
			EK.extend(watchableObj, obj);
            EK.tag("head")[0].appendChild(watchableObj);
            obj = watchableObj;
			obj.onpropertychange = function(){
				var prop = event.propertyName;
				if (prop !== 'onpropertychange') {
                    var nested = obj[prop];
					if (isInterestingObject(nested)) {
                        WSD.moria.util.debug('started watching ' + prop);
                        // unwatch in theory would be what you want to do
                        // here instead of unbinding the handler
                        var handler = obj.onpropertychange;
                        obj.onpropertychange = null;
                        //unwatch(obj[prop]);
                        obj[prop] = watch(nested, triggerObj);
                        obj.onpropertychange = handler;
					}
					triggerObj.trigger("change", prop);
				}
			};
		}
		else {
			// convert Array to 'array-like' Object (i.e. has length property
			// and properties numbered 0 to length-1) so we can watch elements
			if (obj.constructor == Array) {	obj = EK.extend({}, obj); }
			for (var propertyName in obj) {
				obj.watch(propertyName, function(prop, oldval, newval){
					setTimeout(function(){
						triggerObj.trigger("change", prop);
						// Reset the watch points on this object to pick
						// up any added properties. (if you add props, 
						// change any existing prop value on the object and voila!) 
						//obj = watch(obj, triggerObj);
					}, 10);
					return newval;
				});
			}
		}
		return obj;
	}
}();
